package gin

import (
	"net/http"
	"path"
	"regexp"
	"strings"
)

var (
	// reg match english letters for http method name
	regEnLetter = regexp.MustCompile("^[A-Z]+$")
)

// IRouter 定义所有路由器句柄接口，包括单路由器和组路由器。
type IRouter interface {
	IRoute                                     //单点路由
	Group(string, ...HandlerFunc) *RouterGroup //路由组
}

// IRoutes 接口里放的是常用的路由器处理器。
type IRoute interface {
	Use(...HandlerFunc) IRoute
	Handle(string, string, ...HandlerFunc) IRoute
	Any(string, ...HandlerFunc) IRoute
	GET(string, ...HandlerFunc) IRoute
	POST(string, ...HandlerFunc) IRoute
	DELETE(string, ...HandlerFunc) IRoute
	PATCH(string, ...HandlerFunc) IRoute
	PUT(string, ...HandlerFunc) IRoute
	OPTIONS(string, ...HandlerFunc) IRoute
	HEAD(string, ...HandlerFunc) IRoute
	StaticFile(string, string) IRoute
	Static(string, string) IRoute
	StaticFS(string, http.FileSystem) IRoute
}

// RouterGroup 在内部用于配置路由器，RouterGroup与前缀和一组处理程序(中间件)相关联。
type RouterGroup struct {
	Handlers HandlersChain // Handlers 存储着所有中间件
	basePath string
	engine   *Engine
	root     bool
}

var _ IRouter = &RouterGroup{}

// Use 将中间件添加到 RouterGroup.Handlers 中。
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoute{
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

// Group 返回一个新生成的 *RouterGroup，用来分开每个路由组加载不一样的中间件。
//  注册路由组中间件之前要先调用此方法，先生成一个 *RouterGroup，然后在调用 *RouterGroup.Use()注册路由组中间件。
//  例如:
//    group := g.Group("/test_group")
//		 group.Use(middleware.Test()){
//	    group.GET("/test",handler.TestTool)//这里会最终路由和中间件以及handle方法一起写入树节点中
//    }
//
//  例如: 可以将使用通用中间件进行授权的所有路由进行分组。
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	return &RouterGroup{
		Handlers: group.combineHandlers(handlers),
		basePath: group.calculateAbsolutePath(relativePath),
		engine:   group.engine,
	}
}

// BasePath 返回路由器组的基本路径。
//  For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api".
func (group *RouterGroup) BasePath() string {
	return group.basePath
}
// handle 不管哪种请求方式最终都会调用 RouterGroup.handle，这个方法主要有两个作用:
//  1、处理路由的格式,将路由拼成 ‘/’ 字符开头的路由。
//  2、复制一份 RouterGroup.Handlers，加上相应这次路由的handle方法，组成一个list放入树节点中。
//  3、最后调用trees.addRoute增加节点。
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoute{
	absolutePath := group.calculateAbsolutePath(relativePath)
	handlers = group.combineHandlers(handlers)
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}

// Handle 使用给定的路径和方法注册新的请求句柄和中间件。
//  最后一个处理程序应该是真正的处理程序，其他处理程序应该是可以并且应该在不同路由之间共享的中间件。请参阅GitHub中的示例代码。
//  对于GET，POST，PUT，PATCH和DELETE请求，可以使用相应的快捷功能。
//  此功能用于批量加载，并允许使用不常用的，非标准化或自定义的方法（例如，用于与代理进行内部通信）。
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoute{
	if matched := regEnLetter.MatchString(httpMethod); !matched {
		panic("http method " + httpMethod + " is not valid")
	}
	return group.handle(httpMethod, relativePath, handlers)
}

// POST is a shortcut for router.Handle("POST", path, handle).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoute{
	return group.handle(http.MethodPost, relativePath, handlers)
}

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoute{
	return group.handle(http.MethodGet, relativePath, handlers)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle).
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoute{
	return group.handle(http.MethodDelete, relativePath, handlers)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handle).
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoute{
	return group.handle(http.MethodPatch, relativePath, handlers)
}

// PUT is a shortcut for router.Handle("PUT", path, handle).
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoute{
	return group.handle(http.MethodPut, relativePath, handlers)
}

// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoute{
	return group.handle(http.MethodOptions, relativePath, handlers)
}

// HEAD is a shortcut for router.Handle("HEAD", path, handle).
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoute{
	return group.handle(http.MethodHead, relativePath, handlers)
}

// Any 注册与所有HTTP方法匹配的路由。
//  GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoute{
	group.handle(http.MethodGet, relativePath, handlers)
	group.handle(http.MethodPost, relativePath, handlers)
	group.handle(http.MethodPut, relativePath, handlers)
	group.handle(http.MethodPatch, relativePath, handlers)
	group.handle(http.MethodHead, relativePath, handlers)
	group.handle(http.MethodOptions, relativePath, handlers)
	group.handle(http.MethodDelete, relativePath, handlers)
	group.handle(http.MethodConnect, relativePath, handlers)
	group.handle(http.MethodTrace, relativePath, handlers)
	return group.returnObj()
}

// StaticFile 注册单个路由，以便为本地文件系统的单个文件提供服务。
//  例如: router.StaticFile("favicon.ico", "./resources/favicon.ico")
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoute{
	if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
		panic("URL parameters can not be used when serving a static file")
	}
	handler := func(c *Context) {
		c.File(filepath)
	}
	group.GET(relativePath, handler)
	group.HEAD(relativePath, handler)
	return group.returnObj()
}

// Static 提供给定文件系统根目录下的文件。
//
// 内部使用http.FileServer，因此使用http.NotFound代替路由器的NotFound处理程序。
//  要使用操作系统的文件系统实现:
//  use :
//     router.Static("/static", "/var/www")
func (group *RouterGroup) Static(relativePath, root string) IRoute{
	return group.StaticFS(relativePath, Dir(root, false))
}

// StaticFS 就像 Static() 一样工作，但是可以改用自定义 http.FileSystem。
//  Gin默认用户: gin.Dir()
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoute{
	if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
		panic("URL parameters can not be used when serving a static folder")
	}
	handler := group.createStaticHandler(relativePath, fs)
	urlPattern := path.Join(relativePath, "/*filepath")

	// Register GET and HEAD handlers
	group.GET(urlPattern, handler)
	group.HEAD(urlPattern, handler)
	return group.returnObj()
}

func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
	absolutePath := group.calculateAbsolutePath(relativePath)
	fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))

	return func(c *Context) {
		if _, noListing := fs.(*onlyFilesFS); noListing {
			c.Writer.WriteHeader(http.StatusNotFound)
		}

		file := c.Param("filepath")
		// Check if file exists and/or if we have permission to access it
		f, err := fs.Open(file)
		if err != nil {
			c.Writer.WriteHeader(http.StatusNotFound)
			c.handlers = group.engine.noRoute
			// Reset index
			c.index = -1
			return
		}
		f.Close()

		fileServer.ServeHTTP(c.Writer, c.Request)
	}
}

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
	finalSize := len(group.Handlers) + len(handlers)
	if finalSize >= int(abortIndex) {
		panic("too many handlers")
	}
	mergedHandlers := make(HandlersChain, finalSize)
	copy(mergedHandlers, group.Handlers)
	copy(mergedHandlers[len(group.Handlers):], handlers)
	return mergedHandlers
}

func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
	return joinPaths(group.basePath, relativePath)
}

func (group *RouterGroup) returnObj() IRoute{
	if group.root {
		return group.engine
	}
	return group
}
